3 use MediaWiki\MediaWikiServices
;
4 use Wikimedia\TestingAccessWrapper
;
7 * Test class for ChangesListSpecialPage class
9 * Copyright © 2011-, Antoine Musso, Stephane Bisson, Matthew Flaschen
11 * @author Antoine Musso
12 * @author Stephane Bisson
13 * @author Matthew Flaschen
16 * @covers ChangesListSpecialPage
18 class ChangesListSpecialPageTest
extends AbstractChangesListSpecialPageTestCase
{
19 protected function getPage() {
20 $mock = $this->getMockBuilder( ChangesListSpecialPage
::class )
23 'ChangesListSpecialPage',
27 ->setMethods( [ 'getPageTitle' ] )
28 ->getMockForAbstractClass();
30 $mock->method( 'getPageTitle' )->willReturn(
31 Title
::makeTitle( NS_SPECIAL
, 'ChangesListSpecialPage' )
34 $mock = TestingAccessWrapper
::newFromObject(
41 private function buildQuery(
42 $requestOptions = null,
45 $context = new RequestContext
;
46 $context->setRequest( new FauxRequest( $requestOptions ) );
48 $context->setUser( $user );
51 $this->changesListSpecialPage
->setContext( $context );
52 $this->changesListSpecialPage
->filterGroups
= [];
53 $formOptions = $this->changesListSpecialPage
->setup( null );
55 # Filter out rc_timestamp conditions which depends on the test runtime
56 # This condition is not needed as of march 2, 2011 -- hashar
57 # @todo FIXME: Find a way to generate the correct rc_timestamp
61 $queryConditions = [];
66 [ $this->changesListSpecialPage
, 'buildQuery' ],
77 $queryConditions = array_filter(
79 'ChangesListSpecialPageTest::filterOutRcTimestampCondition'
82 return $queryConditions;
85 /** helper to test SpecialRecentchanges::buildQuery() */
86 private function assertConditions(
88 $requestOptions = null,
92 $queryConditions = $this->buildQuery( $requestOptions, $user );
95 self
::normalizeCondition( $expected ),
96 self
::normalizeCondition( $queryConditions ),
101 private static function normalizeCondition( $conds ) {
102 $dbr = wfGetDB( DB_REPLICA
);
103 $normalized = array_map(
104 function ( $k, $v ) use ( $dbr ) {
105 if ( is_array( $v ) ) {
108 // (Ab)use makeList() to format only this entry
109 return $dbr->makeList( [ $k => $v ], Database
::LIST_AND
);
111 array_keys( $conds ),
118 /** return false if condition begins with 'rc_timestamp ' */
119 private static function filterOutRcTimestampCondition( $var ) {
120 return ( is_array( $var ) ||
strpos( $var, 'rc_timestamp ' ) === false );
123 public function testRcNsFilter() {
124 $this->assertConditions(
126 "rc_namespace = '0'",
129 'namespace' => NS_MAIN
,
131 "rc conditions with one namespace"
135 public function testRcNsFilterInversion() {
136 $this->assertConditions(
138 "rc_namespace != '0'",
141 'namespace' => NS_MAIN
,
144 "rc conditions with namespace inverted"
148 public function testRcNsFilterMultiple() {
149 $this->assertConditions(
151 "rc_namespace IN ('1','2','3')",
154 'namespace' => '1;2;3',
156 "rc conditions with multiple namespaces"
160 public function testRcNsFilterMultipleAssociated() {
161 $this->assertConditions(
163 "rc_namespace IN ('0','1','4','5','6','7')",
166 'namespace' => '1;4;7',
169 "rc conditions with multiple namespaces and associated"
173 public function testRcNsFilterAssociatedSpecial() {
174 $this->assertConditions(
176 "rc_namespace IN ('-1','0','1')",
179 'namespace' => '1;-1',
182 "rc conditions with associated and special namespace"
186 public function testRcNsFilterMultipleAssociatedInvert() {
187 $this->assertConditions(
189 "rc_namespace NOT IN ('2','3','8','9')",
192 'namespace' => '2;3;9',
196 "rc conditions with multiple namespaces, associated and inverted"
200 public function testRcNsFilterMultipleInvert() {
201 $this->assertConditions(
203 "rc_namespace NOT IN ('1','2','3')",
206 'namespace' => '1;2;3',
209 "rc conditions with multiple namespaces inverted"
213 public function testRcNsFilterAllContents() {
214 $namespaces = MediaWikiServices
::getInstance()->getNamespaceInfo()->getSubjectNamespaces();
215 $this->assertConditions(
217 'rc_namespace IN (' . $this->db
->makeList( $namespaces ) . ')',
220 'namespace' => 'all-contents',
222 "rc conditions with all-contents"
226 public function testRcHidemyselfFilter() {
227 $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', SCHEMA_COMPAT_NEW
);
228 $this->overrideMwServices();
230 $user = $this->getTestUser()->getUser();
231 $user->getActorId( wfGetDB( DB_MASTER
) );
232 $this->assertConditions(
234 "NOT((rc_actor = '{$user->getActorId()}'))",
239 "rc conditions: hidemyself=1 (logged in)",
243 $user = User
::newFromName( '10.11.12.13', false );
244 $id = $user->getActorId( wfGetDB( DB_MASTER
) );
245 $this->assertConditions(
247 "NOT((rc_actor = '{$user->getActorId()}'))",
252 "rc conditions: hidemyself=1 (anon)",
257 public function testRcHidemyselfFilter_old() {
259 'wgActorTableSchemaMigrationStage', SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD
261 $this->overrideMwServices();
263 $user = $this->getTestUser()->getUser();
264 $user->getActorId( wfGetDB( DB_MASTER
) );
265 $this->assertConditions(
267 "NOT((rc_user = '{$user->getId()}'))",
272 "rc conditions: hidemyself=1 (logged in)",
276 $user = User
::newFromName( '10.11.12.13', false );
277 $id = $user->getActorId( wfGetDB( DB_MASTER
) );
278 $this->assertConditions(
280 "NOT((rc_user_text = '10.11.12.13'))",
285 "rc conditions: hidemyself=1 (anon)",
290 public function testRcHidebyothersFilter() {
291 $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', SCHEMA_COMPAT_NEW
);
292 $this->overrideMwServices();
294 $user = $this->getTestUser()->getUser();
295 $user->getActorId( wfGetDB( DB_MASTER
) );
296 $this->assertConditions(
298 "(rc_actor = '{$user->getActorId()}')",
303 "rc conditions: hidebyothers=1 (logged in)",
307 $user = User
::newFromName( '10.11.12.13', false );
308 $id = $user->getActorId( wfGetDB( DB_MASTER
) );
309 $this->assertConditions(
311 "(rc_actor = '{$user->getActorId()}')",
316 "rc conditions: hidebyothers=1 (anon)",
321 public function testRcHidebyothersFilter_old() {
323 'wgActorTableSchemaMigrationStage', SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD
325 $this->overrideMwServices();
327 $user = $this->getTestUser()->getUser();
328 $user->getActorId( wfGetDB( DB_MASTER
) );
329 $this->assertConditions(
331 "(rc_user_text = '{$user->getName()}')",
336 "rc conditions: hidebyothers=1 (logged in)",
340 $user = User
::newFromName( '10.11.12.13', false );
341 $id = $user->getActorId( wfGetDB( DB_MASTER
) );
342 $this->assertConditions(
344 "(rc_user_text = '10.11.12.13')",
349 "rc conditions: hidebyothers=1 (anon)",
354 public function testRcHidepageedits() {
355 $this->assertConditions(
360 'hidepageedits' => 1,
362 "rc conditions: hidepageedits=1"
366 public function testRcHidenewpages() {
367 $this->assertConditions(
374 "rc conditions: hidenewpages=1"
378 public function testRcHidelog() {
379 $this->assertConditions(
386 "rc conditions: hidelog=1"
390 public function testRcHidehumans() {
391 $this->assertConditions(
399 "rc conditions: hidebots=0 hidehumans=1"
403 public function testRcHidepatrolledDisabledFilter() {
404 $this->setMwGlobals( 'wgUseRCPatrol', false );
405 $user = $this->getTestUser()->getUser();
406 $this->assertConditions(
410 'hidepatrolled' => 1,
412 "rc conditions: hidepatrolled=1 (user not allowed)",
417 public function testRcHideunpatrolledDisabledFilter() {
418 $this->setMwGlobals( 'wgUseRCPatrol', false );
419 $user = $this->getTestUser()->getUser();
420 $this->assertConditions(
424 'hideunpatrolled' => 1,
426 "rc conditions: hideunpatrolled=1 (user not allowed)",
431 public function testRcHidepatrolledFilter() {
432 $user = $this->getTestSysop()->getUser();
433 $this->assertConditions(
438 'hidepatrolled' => 1,
440 "rc conditions: hidepatrolled=1",
445 public function testRcHideunpatrolledFilter() {
446 $user = $this->getTestSysop()->getUser();
447 $this->assertConditions(
449 'rc_patrolled' => [ 1, 2 ],
452 'hideunpatrolled' => 1,
454 "rc conditions: hideunpatrolled=1",
459 public function testRcReviewStatusFilter() {
460 $user = $this->getTestSysop()->getUser();
461 $this->assertConditions(
466 'reviewStatus' => 'manual'
468 "rc conditions: reviewStatus=manual",
471 $this->assertConditions(
473 'rc_patrolled' => [ 0, 2 ],
476 'reviewStatus' => 'unpatrolled;auto'
478 "rc conditions: reviewStatus=unpatrolled;auto",
483 public function testRcHideminorFilter() {
484 $this->assertConditions(
491 "rc conditions: hideminor=1"
495 public function testRcHidemajorFilter() {
496 $this->assertConditions(
503 "rc conditions: hidemajor=1"
507 public function testHideCategorization() {
508 $this->assertConditions(
514 'hidecategorization' => 1
516 "rc conditions: hidecategorization=1"
520 public function testFilterUserExpLevelAll() {
521 $this->assertConditions(
526 'userExpLevel' => 'registered;unregistered;newcomer;learner;experienced',
528 "rc conditions: userExpLevel=registered;unregistered;newcomer;learner;experienced"
532 public function testFilterUserExpLevelRegisteredUnregistered() {
533 $this->assertConditions(
538 'userExpLevel' => 'registered;unregistered',
540 "rc conditions: userExpLevel=registered;unregistered"
544 public function testFilterUserExpLevelRegisteredUnregisteredLearner() {
545 $this->assertConditions(
550 'userExpLevel' => 'registered;unregistered;learner',
552 "rc conditions: userExpLevel=registered;unregistered;learner"
556 public function testFilterUserExpLevelAllExperienceLevels() {
557 $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', SCHEMA_COMPAT_NEW
);
558 $this->overrideMwServices();
560 $this->assertConditions(
563 'actor_rc_user.actor_user IS NOT NULL',
566 'userExpLevel' => 'newcomer;learner;experienced',
568 "rc conditions: userExpLevel=newcomer;learner;experienced"
572 public function testFilterUserExpLevelAllExperienceLevels_old() {
574 'wgActorTableSchemaMigrationStage', SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD
576 $this->overrideMwServices();
578 $this->assertConditions(
584 'userExpLevel' => 'newcomer;learner;experienced',
586 "rc conditions: userExpLevel=newcomer;learner;experienced"
590 public function testFilterUserExpLevelRegistrered() {
591 $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', SCHEMA_COMPAT_NEW
);
592 $this->overrideMwServices();
594 $this->assertConditions(
597 'actor_rc_user.actor_user IS NOT NULL',
600 'userExpLevel' => 'registered',
602 "rc conditions: userExpLevel=registered"
606 public function testFilterUserExpLevelRegistrered_old() {
608 'wgActorTableSchemaMigrationStage', SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD
610 $this->overrideMwServices();
612 $this->assertConditions(
618 'userExpLevel' => 'registered',
620 "rc conditions: userExpLevel=registered"
624 public function testFilterUserExpLevelUnregistrered() {
625 $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', SCHEMA_COMPAT_NEW
);
626 $this->overrideMwServices();
628 $this->assertConditions(
631 'actor_rc_user.actor_user IS NULL',
634 'userExpLevel' => 'unregistered',
636 "rc conditions: userExpLevel=unregistered"
640 public function testFilterUserExpLevelUnregistrered_old() {
642 'wgActorTableSchemaMigrationStage', SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD
644 $this->overrideMwServices();
646 $this->assertConditions(
652 'userExpLevel' => 'unregistered',
654 "rc conditions: userExpLevel=unregistered"
658 public function testFilterUserExpLevelRegistreredOrLearner() {
659 $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', SCHEMA_COMPAT_NEW
);
660 $this->overrideMwServices();
662 $this->assertConditions(
665 'actor_rc_user.actor_user IS NOT NULL',
668 'userExpLevel' => 'registered;learner',
670 "rc conditions: userExpLevel=registered;learner"
674 public function testFilterUserExpLevelRegistreredOrLearner_old() {
676 'wgActorTableSchemaMigrationStage', SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD
678 $this->overrideMwServices();
680 $this->assertConditions(
686 'userExpLevel' => 'registered;learner',
688 "rc conditions: userExpLevel=registered;learner"
692 public function testFilterUserExpLevelUnregistreredOrExperienced() {
693 $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', SCHEMA_COMPAT_NEW
);
694 $this->overrideMwServices();
696 $conds = $this->buildQuery( [ 'userExpLevel' => 'unregistered;experienced' ] );
699 '/\(actor_rc_user\.actor_user IS NULL\) OR '
700 . '\(\(user_editcount >= 500\) AND \(user_registration <= \'[^\']+\'\)\)/',
702 "rc conditions: userExpLevel=unregistered;experienced"
706 public function testFilterUserExpLevelUnregistreredOrExperienced_old() {
708 'wgActorTableSchemaMigrationStage', SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD
710 $this->overrideMwServices();
712 $conds = $this->buildQuery( [ 'userExpLevel' => 'unregistered;experienced' ] );
715 '/\(rc_user = 0\) OR '
716 . '\(\(user_editcount >= 500\) AND \(user_registration <= \'[^\']+\'\)\)/',
718 "rc conditions: userExpLevel=unregistered;experienced"
722 public function testFilterUserExpLevel() {
724 $this->setMwGlobals( [
725 'wgLearnerEdits' => 10,
726 'wgLearnerMemberSince' => 4,
727 'wgExperiencedUserEdits' => 500,
728 'wgExperiencedUserMemberSince' => 30,
731 $this->createUsers( [
732 'Newcomer1' => [ 'edits' => 2, 'days' => 2 ],
733 'Newcomer2' => [ 'edits' => 12, 'days' => 3 ],
734 'Newcomer3' => [ 'edits' => 8, 'days' => 5 ],
735 'Learner1' => [ 'edits' => 15, 'days' => 10 ],
736 'Learner2' => [ 'edits' => 450, 'days' => 20 ],
737 'Learner3' => [ 'edits' => 460, 'days' => 33 ],
738 'Learner4' => [ 'edits' => 525, 'days' => 28 ],
739 'Experienced1' => [ 'edits' => 538, 'days' => 33 ],
743 $this->assertArrayEquals(
744 [ 'Newcomer1', 'Newcomer2', 'Newcomer3' ],
745 $this->fetchUsers( [ 'newcomer' ], $now )
748 // newcomers and learner
749 $this->assertArrayEquals(
751 'Newcomer1', 'Newcomer2', 'Newcomer3',
752 'Learner1', 'Learner2', 'Learner3', 'Learner4',
754 $this->fetchUsers( [ 'newcomer', 'learner' ], $now )
757 // newcomers and more learner
758 $this->assertArrayEquals(
760 'Newcomer1', 'Newcomer2', 'Newcomer3',
763 $this->fetchUsers( [ 'newcomer', 'experienced' ], $now )
767 $this->assertArrayEquals(
768 [ 'Learner1', 'Learner2', 'Learner3', 'Learner4' ],
769 $this->fetchUsers( [ 'learner' ], $now )
772 // more experienced only
773 $this->assertArrayEquals(
775 $this->fetchUsers( [ 'experienced' ], $now )
778 // learner and more experienced
779 $this->assertArrayEquals(
781 'Learner1', 'Learner2', 'Learner3', 'Learner4',
784 $this->fetchUsers( [ 'learner', 'experienced' ], $now )
788 private function createUsers( $specs, $now ) {
789 $dbw = wfGetDB( DB_MASTER
);
790 foreach ( $specs as $name => $spec ) {
794 'editcount' => $spec['edits'],
795 'registration' => $dbw->timestamp( $this->daysAgo( $spec['days'], $now ) ),
802 private function fetchUsers( $filters, $now ) {
811 call_user_func_array(
812 [ $this->changesListSpecialPage
, 'filterOnUserExperienceLevel' ],
814 get_class( $this->changesListSpecialPage
),
815 $this->changesListSpecialPage
->getContext(),
816 $this->changesListSpecialPage
->getDB(),
827 // @todo: This is not at all safe or sane. It just blindly assumes
828 // nothing in $conds depends on any other tables.
829 $result = wfGetDB( DB_MASTER
)->select(
832 array_filter( $conds ) +
[ 'user_email' => 'ut' ]
836 foreach ( $result as $row ) {
837 $usernames[] = $row->user_name
;
843 private function daysAgo( $days, $now ) {
844 $secondsPerDay = 86400;
845 return $now - $days * $secondsPerDay;
848 public function testGetStructuredFilterJsData() {
849 $this->changesListSpecialPage
->filterGroups
= [];
853 'name' => 'gub-group',
854 'title' => 'gub-group-title',
855 'class' => ChangesListBooleanFilterGroup
::class,
859 'label' => 'foo-label',
860 'description' => 'foo-description',
862 'showHide' => 'showhidefoo',
867 'label' => 'bar-label',
868 'description' => 'bar-description',
876 'name' => 'des-group',
877 'title' => 'des-group-title',
878 'class' => ChangesListStringOptionsFilterGroup
::class,
879 'isFullCoverage' => true,
883 'label' => 'grault-label',
884 'description' => 'grault-description',
888 'label' => 'garply-label',
889 'description' => 'garply-description',
892 'queryCallable' => function () {
894 'default' => ChangesListStringOptionsFilterGroup
::NONE
,
898 'name' => 'unstructured',
899 'class' => ChangesListBooleanFilterGroup
::class,
902 'name' => 'hidethud',
903 'showHide' => 'showhidethud',
909 'showHide' => 'showhidemos',
917 $this->changesListSpecialPage
->registerFiltersFromDefinitions( $definition );
919 $this->assertArrayEquals(
921 // Filters that only display in the unstructured UI are
922 // are not included, and neither are groups that would
923 // be empty due to the above.
926 'name' => 'gub-group',
927 'title' => 'gub-group-title',
928 'type' => ChangesListBooleanFilterGroup
::TYPE
,
933 'label' => 'bar-label',
934 'description' => 'bar-description',
940 'defaultHighlightColor' => null
944 'label' => 'foo-label',
945 'description' => 'foo-description',
951 'defaultHighlightColor' => null
954 'fullCoverage' => true,
959 'name' => 'des-group',
960 'title' => 'des-group-title',
961 'type' => ChangesListStringOptionsFilterGroup
::TYPE
,
963 'fullCoverage' => true,
967 'label' => 'grault-label',
968 'description' => 'grault-description',
973 'defaultHighlightColor' => null
977 'label' => 'garply-label',
978 'description' => 'garply-description',
983 'defaultHighlightColor' => null
988 'default' => ChangesListStringOptionsFilterGroup
::NONE
,
999 'grault-description',
1001 'garply-description',
1004 $this->changesListSpecialPage
->getStructuredFilterJsData(),
1005 /** ordered= */ false,
1010 public function provideParseParameters() {
1012 [ 'hidebots', [ 'hidebots' => true ] ],
1014 [ 'bots', [ 'hidebots' => false ] ],
1016 [ 'hideminor', [ 'hideminor' => true ] ],
1018 [ 'minor', [ 'hideminor' => false ] ],
1020 [ 'hidemajor', [ 'hidemajor' => true ] ],
1022 [ 'hideliu', [ 'hideliu' => true ] ],
1024 [ 'hidepatrolled', [ 'hidepatrolled' => true ] ],
1026 [ 'hideunpatrolled', [ 'hideunpatrolled' => true ] ],
1028 [ 'hideanons', [ 'hideanons' => true ] ],
1030 [ 'hidemyself', [ 'hidemyself' => true ] ],
1032 [ 'hidebyothers', [ 'hidebyothers' => true ] ],
1034 [ 'hidehumans', [ 'hidehumans' => true ] ],
1036 [ 'hidepageedits', [ 'hidepageedits' => true ] ],
1038 [ 'pagedits', [ 'hidepageedits' => false ] ],
1040 [ 'hidenewpages', [ 'hidenewpages' => true ] ],
1042 [ 'hidecategorization', [ 'hidecategorization' => true ] ],
1044 [ 'hidelog', [ 'hidelog' => true ] ],
1047 'userExpLevel=learner;experienced',
1049 'userExpLevel' => 'learner;experienced'
1053 // A few random combos
1055 'bots,hideliu,hidemyself',
1057 'hidebots' => false,
1059 'hidemyself' => true,
1064 'minor,hideanons,categorization',
1066 'hideminor' => false,
1067 'hideanons' => true,
1068 'hidecategorization' => false,
1073 'hidehumans,bots,hidecategorization',
1075 'hidehumans' => true,
1076 'hidebots' => false,
1077 'hidecategorization' => true,
1082 'hidemyself,userExpLevel=newcomer;learner,hideminor',
1084 'hidemyself' => true,
1085 'hideminor' => true,
1086 'userExpLevel' => 'newcomer;learner',
1092 public function provideGetFilterConflicts() {
1096 "expectedConflicts" => false,
1101 "userExpLevel" => "newcomer",
1103 "expectedConflicts" => false,
1107 "hideanons" => true,
1108 "userExpLevel" => "learner",
1110 "expectedConflicts" => false,
1114 "hidemajor" => true,
1115 "hidenewpages" => true,
1116 "hidepageedits" => true,
1117 "hidecategorization" => false,
1119 "hideWikidata" => true,
1121 "expectedConflicts" => true,
1125 "hidemajor" => true,
1126 "hidenewpages" => false,
1127 "hidepageedits" => true,
1128 "hidecategorization" => false,
1130 "hideWikidata" => true,
1132 "expectedConflicts" => true,
1136 "hidemajor" => true,
1137 "hidenewpages" => false,
1138 "hidepageedits" => false,
1139 "hidecategorization" => true,
1141 "hideWikidata" => true,
1143 "expectedConflicts" => false,
1147 "hideminor" => true,
1148 "hidenewpages" => true,
1149 "hidepageedits" => true,
1150 "hidecategorization" => false,
1152 "hideWikidata" => true,
1154 "expectedConflicts" => false,
1160 * @dataProvider provideGetFilterConflicts
1162 public function testGetFilterConflicts( $parameters, $expectedConflicts ) {
1163 $context = new RequestContext
;
1164 $context->setRequest( new FauxRequest( $parameters ) );
1165 $this->changesListSpecialPage
->setContext( $context );
1167 $this->assertEquals(
1169 $this->changesListSpecialPage
->areFiltersInConflict()
1173 public function validateOptionsProvider() {
1176 [ 'hideanons' => 1, 'hideliu' => 1, 'hidebots' => 1 ],
1178 [ 'userExpLevel' => 'unregistered', 'hidebots' => 1, ],
1182 [ 'hideanons' => 1, 'hideliu' => 1, 'hidebots' => 0 ],
1184 [ 'hidebots' => 0, 'hidehumans' => 1 ],
1188 [ 'hideanons' => 1 ],
1190 [ 'userExpLevel' => 'registered' ],
1196 [ 'userExpLevel' => 'unregistered' ],
1200 [ 'hideanons' => 1, 'hidebots' => 1 ],
1202 [ 'userExpLevel' => 'registered', 'hidebots' => 1 ],
1206 [ 'hideliu' => 1, 'hidebots' => 0 ],
1208 [ 'userExpLevel' => 'unregistered', 'hidebots' => 0 ],
1212 [ 'hidemyself' => 1, 'hidebyothers' => 1 ],
1218 [ 'hidebots' => 1, 'hidehumans' => 1 ],
1224 [ 'hidepatrolled' => 1, 'hideunpatrolled' => 1 ],
1230 [ 'hideminor' => 1, 'hidemajor' => 1 ],
1237 [ 'hidepageedits' => 1, 'hidenewpages' => 1, 'hidecategorization' => 1, 'hidelog' => 1, ],